﻿using mozhi.smarterp.Finance.AccountsPayables.Repositories;
using mozhi.smarterp.Finance.Enums;
using mozhi.smarterp.Finance.Payables;
using mozhi.smarterp.Finance.Supplychains.Purchases.Repositories;
using mozhi.smarterp.Finance.Treasuries;
using mozhi.smarterp.Finance.Treasuries.Repositories;
using mozhi.smarterp.Finance.Treasuries.Services;
using Mozhi.Abp.Domain.AuthorCenter;
using Mozhi.Abp.Domain.BasicData;
using Mozhi.Abp.Domain.BasicData.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Guids;

namespace mozhi.smarterp.Finance.AccountsPayables.Services
{
    public class PurchasePayableManager : DocumentDomainService<PurchasePayable, Guid>
    {
        private ISupplierRepository _supplierRepository;
        private ICurrencyRepository _currencyRepository;
        private IDepartmentRepository _departmentRepository;
        private IEmployeeRepository _employeeRepository;
        private IGuidGenerator _guidGenerator;
        private IProductRepository _productRepository;
        private IExpenseItemRepository _expenseItemRepository;
        private ITaxRateRepository _taxRateRepository;
        private IPurchasePayableReceiptRepository _payableReceiptRepository;
        private IOrganizationRepository _organizationRepository;
        private IPayableSettlementRepository _payableSettlementRepository;
        private PaymentManager _paymentManager;
        private IPaymentRepository _paymentRepository;
        private IPurchaseOrderReceiptRepository _purchaseOrderReceiptRepository;
        private IPaymentOrderLinkRepository _paymentOrderLinkRepository;


        public PurchasePayableManager(IPurchasePayableRepository repository, INumberGeneratorRepository numberGeneratorRepository, ISupplierRepository supplierRepository, ICurrencyRepository currencyRepository,
            IDepartmentRepository departmentRepository, IEmployeeRepository employeeRepository, IGuidGenerator guidGenerator, IProductRepository productRepository, IExpenseItemRepository expenseItemRepository, 
            ITaxRateRepository taxRateRepository , IPurchasePayableReceiptRepository payableReceiptRepository , IOrganizationRepository organizationRepository , IPayableSettlementRepository payableSettlementRepository,
            PaymentManager paymentManager , IPaymentRepository paymentRepository , IPurchaseOrderReceiptRepository purchaseOrderReceiptRepository)
            : base(repository, numberGeneratorRepository, DocumentType.PurchasePayable)
        {
            _supplierRepository = supplierRepository;
            _currencyRepository = currencyRepository;
            _employeeRepository = employeeRepository;
            _departmentRepository = departmentRepository;
            _guidGenerator = guidGenerator;
            _productRepository = productRepository;
            _taxRateRepository = taxRateRepository;
            _expenseItemRepository = expenseItemRepository;
            _payableReceiptRepository = payableReceiptRepository;
            _organizationRepository = organizationRepository;
            _payableSettlementRepository = payableSettlementRepository;
            _paymentManager = paymentManager;
            _paymentRepository = paymentRepository;
            _purchaseOrderReceiptRepository = purchaseOrderReceiptRepository;

        }

        public async Task<PurchasePayable> GetAsync(Guid? id, DateTime date, DateTime dueDate, Guid supplierId, Guid currencyId, decimal exchangeRate, PayableDocumentType type, RecognitionModelType recognitionModel,
            Guid? purchaserDepartmentId, Guid? purchaserId, Guid? paymentTermId, bool isCreditNote , string comment)
        {
            var supplier = await _supplierRepository.GetAsync(supplierId);
            var currency = await _currencyRepository.GetAsync(currencyId);

            if (currency != null)
                currency.SetExchangeRate(exchangeRate);

            var purchaserDepartment = purchaserDepartmentId.HasValue ? await _departmentRepository.GetAsync(purchaserDepartmentId.Value) : null;
            var purchaser = purchaserId.HasValue ? await _employeeRepository.GetAsync(purchaserId.Value) : null;

            PurchasePayable payable;

            if (id.HasValue)
            {
                payable = await _repository.GetAsync(id.Value);

                if (payable == null)
                    throw new UserFriendlyException($"采购应付单{id}不存在", "100000");

                payable.Change(date, dueDate, supplier, currency, type, recognitionModel, false, purchaserDepartment, purchaser, paymentTermId, comment);
            }
            else
            {
                var number = await GetNextNumberAsync();

                payable = new PurchasePayable(_guidGenerator.Create(), number, date, dueDate, supplier, currency, type, recognitionModel, false, purchaserDepartment, purchaser, paymentTermId, isCreditNote, comment);
            }

            return payable;
        }

        public async Task<PurchasePayableItem> GetItemAsync(Guid? id, Guid? productId, Guid? expenseItemId, decimal price, Guid? taxRateId, decimal basicQuantity, decimal? amountFr, Guid? expenseBurdenDepartmentId, string remark)
        {
            var product = productId.HasValue ? await _productRepository.GetAsync(productId.Value) : null;
            var expenseItem = expenseItemId.HasValue ? await _expenseItemRepository.GetAsync(expenseItemId.Value) : null;
            var taxRate = taxRateId.HasValue ? await _taxRateRepository.GetAsync(taxRateId.Value) : null;
            var expenseBurdenDepartment = expenseBurdenDepartmentId.HasValue ? await _departmentRepository.GetAsync(expenseBurdenDepartmentId.Value) : null;

            id = id ?? _guidGenerator.Create();

            return new PurchasePayableItem(id.Value, product, expenseItem, price, taxRate, basicQuantity, amountFr, expenseBurdenDepartment, remark);
        }

        public async Task DeleteAsync(Guid id)
        {
            var payable = await _repository.GetAsync(id);

            if (payable.Status != DocumentStatusType.Creation)
                throw new UserFriendlyException($"应付单：{payable.Number}非创建状态，不允许删除", "100000");

            var payableReceipts = await _payableReceiptRepository.GetListAsync(id, null, null, null);

            await _repository.DeleteAsync(id);

            if (payableReceipts.Count > 0)
            {
                var payableReceiptIds = payableReceipts.Select(x => x.Id).ToList();

                await _payableReceiptRepository.DeleteAsync(x => payableReceiptIds.Contains(x.Id));
            }
        }

        public async Task UpdateStatusAsync(Guid id, DocumentStatusType status)
        {
            var payable = await _repository.GetAsync(id);

            if (payable == null)
                throw new UserFriendlyException($"应付单：{id}不存在", "100000");

            //审核的时候要校验单据单据日期
            if (status == DocumentStatusType.Approved || status == DocumentStatusType.PendingApproval)
                await ValidateDocumentDateAsync(payable.Date);

            payable.SetStatus(status);

            //撤销审核要满足几个条件 没有和付款单核销
            if (payable.Status == DocumentStatusType.Approved && status == DocumentStatusType.PendingApproval)
            {
                var payableSettlementCount = await _payableSettlementRepository.GetCountAsync(payable.Id, null);

                if (payableSettlementCount > 0)
                    throw new UserFriendlyException($"应付单：{payable.Number}已存在核销关系，不允许反审核", "100000");
            }
            else if (payable.Status == DocumentStatusType.PendingApproval && status == DocumentStatusType.Approved)
            {
                //预付款自动核销
                await PayableAutoSettmentWithAdvancePaymentAsync(payable);
            }

            await _repository.UpdateAsync(payable);
        }

        public async Task<Guid> PushPaymentAsync(Guid id)
        {
            var payable = await _repository.GetAsync(id);

            if (payable == null)
                throw new UserFriendlyException($"应付单：{id}不存在", "100000");

            if (payable.Status != DocumentStatusType.Approved)
                throw new UserFriendlyException($"应付单：{payable.Number}非审核状态，不允许下推付款单", "100000");

            //检查是否已经全部核销
            var payableSettments = await _payableSettlementRepository.GetListAsync(null, payable.Id);

            var totalSettmentAmountFr = payableSettments.Sum(x => x.SettlementAmountFr);

            if (totalSettmentAmountFr >= payable.TotalAmountFr)
                throw new UserFriendlyException($"应付单：{payable.Number}已全部核销，不允许下推付款单", "100000");

            var remainSettmentAmountFr = payable.TotalAmountFr - totalSettmentAmountFr;

            var payment = await _paymentManager.GetAsync(null, DateTime.UtcNow, null, payable.SupplierId, null, null, TradingPartnerType.Supplier, 
                payable.CurrencyId, payable.ExchangeRate, false, false, null, DocumentStatusType.Creation);

            var paymentItem = await _paymentManager.GetItemAsync(null, remainSettmentAmountFr, null, null);

            payment.SetItems(new List<PaymentItem> { paymentItem });

            //保存关联关系
            var payableSettment = new PayableSettlement(_guidGenerator.Create(), payable.Id, DocumentType.Payment, payment.Id, payment.CurrencyId, payment.AmountFr, payment.Amount, true);

            await _paymentRepository.InsertAsync(payment);

            await _payableSettlementRepository.InsertAsync(payableSettment);

            return payment.Id;

        }

        #region
        private async Task ValidateDocumentDateAsync(DateTime date)
        {
            var organization = await _organizationRepository.GetAsync();

            if (organization == null)
                throw new BusinessException("无法获取到组织信息", "100000");

            organization.ValidateDocumentDate(date);
        }

        private async Task PayableAutoSettmentWithAdvancePaymentAsync(PurchasePayable payable)
        {
            var payableReceipts = await _payableReceiptRepository.GetListAsync(payable.Id, null, null, null);

            if (payableReceipts.Count == 0)
                return;

            //(先找采购入库单，在找订单，在付款单)

            //采购入库单
            var purchaseReceiptIds = payableReceipts.Select(x=>x.ReceiptId).ToList();

            //采购订单
            var purchaseOrderReceipts = await _purchaseOrderReceiptRepository.GetListAsync(purchaseReceiptIds);

            if(purchaseOrderReceipts.Count==0)
                return;

            var purchaseOrderIds = purchaseOrderReceipts.Select(x=>x.OrderId).ToList();

            var paymentOrderLinks = await _paymentOrderLinkRepository.GetListAsync(purchaseOrderIds);

            if (paymentOrderLinks.Count == 0)
                return;

            var paymentIds = paymentOrderLinks.Select(x=>x.PaymentId).ToList();
            
            var payments = await _paymentRepository.GetListAsync(paymentIds);

            payments = payments.Where(x=>x.Status == DocumentStatusType.Approved).ToList();

            if(payments==null)
                return;

            var payableSettlments = new List<PayableSettlement>();

            payments.ForEach(payment =>
            {
                var payableSettlment = new PayableSettlement(_guidGenerator.Create(), payable.Id, DocumentType.Payment, payment.Id, payment.CurrencyId, payment.AmountFr, payment.Amount, true);
                payableSettlments.Add(payableSettlment);
            });
            await _payableSettlementRepository.InsertManyAsync(payableSettlments);
        }
        #endregion
    }
}
